//******************************************************************
//
// Copyright 2014 Intel Corporation.
//
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

#include <signal.h>
#include <thread>
#include <functional>
#include "client.h"
#include "namedefs.h"
#include "sensorButton.h"
using namespace Sensors;

IoTClient::IoTClient()
{
    cout << "Running IoTClient constructor" << endl;
    initializePlatform();
    setupResources();
    m_btnRepresentation.setValue(BTN_RESOURCE_KEY, 0);
    SetupPins();
}

IoTClient::~IoTClient()
{
    cout << "Running IoTClient destructor" << endl;
    ClosePins();
}

void IoTClient::initializePlatform()
{
    m_platformConfig = make_shared<PlatformConfig>(ServiceType::InProc, ModeType::Both, "0.0.0.0",
                                                   0, OC::QualityOfService::HighQos);
    OCPlatform::Configure(*m_platformConfig);
    m_resourceDiscoveryCallback = bind(&IoTClient::discoveredResource, this, placeholders::_1);
}

void IoTClient::setupResources()
{
	EntityHandler cb2 = bind(&IoTClient::btnEntityHandler, this, placeholders::_1);
    createResource(BTN_RESOURCE_ENDPOINT, BTN_RESOURCE_TYPE, cb2, m_btnResource);
    IoTObserverCb btnObsCb = bind(&IoTClient::btnObserverLoop, this);
    m_btnObserverLoop = make_shared<IoTObserver>(btnObsCb);
}

void IoTClient::createResource(string Uri, string Type, EntityHandler Cb, OCResourceHandle& Handle)
{
    string resourceUri = Uri;
    string resourceType = Type;
    string resourceInterface = EDISON_RESOURCE_INTERFACE;
    uint8_t resourceFlag = OC_DISCOVERABLE | OC_OBSERVABLE;

	OCStackResult result = OCPlatform::registerResource(Handle, resourceUri, resourceType, resourceInterface, Cb, resourceFlag);
	
    if (result != OC_STACK_OK)
        cerr << "Could not create " << Type << " resource" << endl;
    else
        cout << "Successfully created " << Type << " resource" << endl;
}

void IoTClient::btnObserverLoop()
{
    usleep(150000);
    //cout << "Btn Observer Callback" << endl;
    shared_ptr<OCResourceResponse> resourceResponse(new OCResourceResponse());
    resourceResponse->setErrorCode(200);
    resourceResponse->setResourceRepresentation(getBtnRepresentation(), EDISON_RESOURCE_INTERFACE);
    OCStackResult result = OCPlatform::notifyListOfObservers(m_btnResource,
                                                             m_btnObservers,
                                                             resourceResponse);
    if (result == OC_STACK_NO_OBSERVERS)
    {
        cout << "No more observers..Stopping observer loop..." << endl;
        m_btnObserverLoop->stop();
    }
}

OCRepresentation IoTClient::getBtnRepresentation()
{
    m_btnRepresentation.setValue(BTN_RESOURCE_KEY, GetButtonState());
    return m_btnRepresentation;
}

OCEntityHandlerResult IoTClient::btnEntityHandler(shared_ptr<OCResourceRequest> Request)
{
	OCEntityHandlerResult result = OC_EH_ERROR;
    if (Request)
    {
        string requestType = Request->getRequestType();
        int requestFlag = Request->getRequestHandlerFlag();
        if (requestFlag & RequestHandlerFlag::RequestFlag)
        {
            auto Response = std::make_shared<OC::OCResourceResponse>();
            Response->setRequestHandle(Request->getRequestHandle());
            Response->setResourceHandle(Request->getResourceHandle());
            if (requestType == "GET")
            {
                cout << "GET request for btn reading" << endl;
                if (Response)
                {
                    Response->setErrorCode(200);
                    Response->setResponseResult(OC_EH_OK);
                    Response->setResourceRepresentation(getBtnRepresentation());
                    if (OCPlatform::sendResponse(Response) == OC_STACK_OK)
                    {
                        result = OC_EH_OK;
                    }
                }
            }
            else
            {
                Response->setResponseResult(OC_EH_ERROR);
                OCPlatform::sendResponse(Response);
                cerr << "Unsupported request type" << endl;
                return result;
            }
        }
        if (requestFlag & RequestHandlerFlag::ObserverFlag)
        {
            ObservationInfo observationInfo = Request->getObservationInfo();
            if (ObserveAction::ObserveRegister == observationInfo.action)
            {
                cout << "Starting observer for btn sensor" << endl;
                m_btnObservers.push_back(observationInfo.obsId);
                m_btnObserverLoop->start();
            }
            else if (ObserveAction::ObserveUnregister == observationInfo.action)
            {
                m_btnObservers.erase(
                        remove(m_btnObservers.begin(), m_btnObservers.end(),
                               observationInfo.obsId),
                        m_btnObservers.end());
            }
        }
    }
    return result;
}

void IoTClient::findResource()
{
    string coap_multicast_discovery = string(OC_WELL_KNOWN_QUERY "?if=" EDISON_RESOURCE_INTERFACE);
    OCPlatform::findResource("", coap_multicast_discovery.c_str(), m_resourceDiscoveryCallback,
                             OC::QualityOfService::LowQos);
}

void IoTClient::discoveredResource(shared_ptr<OCResource> Resource)
{
    try
    {
        if (Resource)
        {
            string resourceUri = Resource->uri();
            string hostAddress = Resource->host();

            cout << "\nFound Resource" << endl << "Resource Types:" << endl;
            for (auto& resourceTypes : Resource->getResourceTypes())
            {
                cout << "\t" << resourceTypes << endl;
            }

            cout << "Resource Interfaces: " << endl;
            for (auto& resourceInterfaces : Resource->getResourceInterfaces())
            {
                cout << "\t" << resourceInterfaces << endl;
            }
            cout << "Resource uri: " << resourceUri << endl;
            cout << "host: " << hostAddress << endl;

            if (resourceUri == LIGHT_RESOURCE_ENDPOINT)
            {
                m_ambientLightSensor = make_shared<AmbientLight>(Resource);
            }
            else if (resourceUri == LED_RESOURCE_ENDPOINT)
            {
                m_platformLED = make_shared<LED>(Resource);
            }
        }
        //IoTClient::DisplayMenu();
    }
    catch (OCException& ex)
    {
        cerr << "Caught exception in discoveredResource: " << ex.reason() << endl;
    }
}

shared_ptr<AmbientLight> IoTClient::getAmbientLightSensor()
{
    return m_ambientLightSensor;
}

shared_ptr<LED> IoTClient::getPlatformLED()
{
    return m_platformLED;
}

AmbientLight::AmbientLight(shared_ptr<OCResource> Resource)
{
    m_isObserved = false;
    m_resourceHandle = Resource;
    m_GETCallback = bind(&AmbientLight::onGet, this, placeholders::_1, placeholders::_2,
                         placeholders::_3);
    m_OBSERVECallback = bind(&AmbientLight::onObserve, this, placeholders::_1, placeholders::_2,
                             placeholders::_3, placeholders::_4);
}

AmbientLight::~AmbientLight()
{

}

void AmbientLight::startObserve()
{
    if (!m_isObserved)
    {
        cout << "Started observing ambient light sensor" << endl;
        m_resourceHandle->observe(ObserveType::Observe, QueryParamsMap(), m_OBSERVECallback);
    }
    m_isObserved = true;
}

void AmbientLight::stopObserve()
{
    if (m_isObserved)
    {
        m_resourceHandle->cancelObserve();
        cout << "Stopped observing ambient light sensor" << endl;
    }
    m_isObserved = false;
}

void AmbientLight::onObserve(const HeaderOptions headerOptions, const OCRepresentation& rep,
                             int eCode, int sequenceNumber)
{
    if (eCode == OC_STACK_OK)
    {
        int value;
        rep.getValue(LIGHT_RESOURCE_KEY, value);
        cout << "Observing AmbientLightSensor: Current ambient light reading is " << value << endl;
        cout << "Sequence number: " << sequenceNumber << endl;
    }
    else
    {
        cerr << "TemperatureSensor: Observer response error" << endl;
    }
}

void AmbientLight::get()
{
    QueryParamsMap params;
    m_resourceHandle->get(params, m_GETCallback);
}

void AmbientLight::onGet(const HeaderOptions& headerOptions, const OCRepresentation& representation,
                         int errCode)
{
    if (errCode == OC_STACK_OK)
    {
        int value;
        representation.getValue(LIGHT_RESOURCE_KEY, value);
        cout << endl << endl << "Current ambient light sensor reading: " << value << endl;
    }
    else {
        cerr << endl << endl << "Error in GET response from ambient light sensor resource" << endl;
    }
    //IoTClient::DisplayMenu();
}

LED::LED(shared_ptr<OCResource> Resource)
{
    m_resourceHandle = Resource;
    m_GETCallback = bind(&LED::onGet, this, placeholders::_1, placeholders::_2, placeholders::_3);
    m_PUTCallback = bind(&LED::onPut, this, placeholders::_1, placeholders::_2, placeholders::_3);
}

LED::~LED()
{

}

void LED::get()
{
    QueryParamsMap params;
    m_resourceHandle->get(params, m_GETCallback);
}

void LED::put(int Switch)
{
    QueryParamsMap params;
    OCRepresentation rep;
    rep.setValue(LED_RESOURCE_KEY, Switch);
    m_resourceHandle->put(rep, params, m_PUTCallback);
}

void LED::onGet(const HeaderOptions& headerOptions, const OCRepresentation& representation,
                int errCode)
{
    if (errCode == OC_STACK_OK)
    {
        int value;
        representation.getValue(LED_RESOURCE_KEY, value);
        cout << endl << endl << "LED switch state is: " << value << endl;
    }
    else {
        cerr << endl << endl << "Error in GET response from LED resource" << endl;
    }
    //IoTClient::DisplayMenu();
}

void LED::onPut(const HeaderOptions& headerOptions, const OCRepresentation& representation,
                int errCode)
{
    if (errCode == OC_STACK_OK)
    {
        int value;
        representation.getValue(LED_RESOURCE_KEY, value);
        cout << endl << endl << "Set LED switch to: " << value << endl;
    }
    else {
        cerr << endl << endl << "Error in PUT response from LED resource" << endl;
    }
    //IoTClient::DisplayMenu();
}

void IoTClient::DisplayMenu()
{
    //cout << "\nEnter:\n0) Display this menu\n1) Get ambient light reading\n2) Start Ambient Light Observer\n3) Stop Ambient Light Observer\n4) Turn LED ON\n5) Turn LED OFF\n6) Quit\n";
}

int main()
{
    IoTClient client;
    cout << "Performing Discovery..." << endl;
    client.findResource();
   	pause();
    return 0;
}
